<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!--[if lt IE 9]>
  <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<![endif]-->
<title>VIM 中文用户手册: 使用 Vim9 脚本编写插件</title>
<link rel="stylesheet" href="vim-stylesheet.css" type="text/css" />
<link rel="canonical" href="https://yianwillis.github.io/vimcdoc/doc/usr_52.html" />
<script type="text/javascript" src="vimcdoc.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<nav id=banner>
<form action=tags.html target="tag_iframe">
  <input type="text" name="tag" id="tag" placeholder="标签搜索">
</form>
<iframe name="tag_iframe" src=""></iframe>
<a href="help.html">帮助总览</a> &middot;
<hr/>
<a href="quickref.html">快速参考</a> &middot;
<a href="index.html">命令索引</a> &middot;
<a href="eval.html#functions">函数列表</a> &middot;
<a href="quickref.html#option-list">选项列表</a> &middot;
<hr/>
<a href="usr_toc.html">用户手册</a> &middot;
<a href="help.html#reference_toc">参考手册</a> &middot;
</nav>

<header>
<h2>usr_52</h2>
</header>
<article id=outer>
<section class=inner>
<b class="vimtag"> <a name="usr_52.txt">usr_52.txt</a> </b>    适用于 Vim 9.0 版本。   最近更新: 2023年2月

                     <code class="vim">VIM 用户手册 - by Bram Moolenaar</code>
                                <code class="vim">译者</code>: Willis

                               编写更大型插件

插件在开始完成更复杂的功能时，会越来越庞大。本文件说明如何确保仍然可以快速载
入，和如何把大文件分割成小部分。

 <a href="usr_52.html#52.1">52.1</a>   导出和导入
 <a href="usr_52.html#52.2">52.2</a>   自动载入
 <a href="usr_52.html#52.3">52.3</a>   不经导入/导出的自动载入
 <a href="usr_52.html#52.4">52.4</a>   可用的其它机制
 <a href="usr_52.html#52.5">52.5</a>   在老式脚本中使用 Vim9 脚本

        下一章:  <a href="usr_90.html#usr_90.txt">usr_90.txt</a>   安装 Vim
        前一章:  <a href="usr_51.html#usr_51.txt">usr_51.txt</a>   创建插件
          目录:  <a href="usr_toc.html#usr_toc.txt">usr_toc.txt</a> 

</section><hr class="doubleline" /><section class=inner>
<h4><b class="vimtag"> <a name="52.1">52.1</a> </b>  导出和导入</h4>
Vim9 脚本设计使大型 Vim 脚本的编写更为容易。它看来更像其它脚本语言，尤其是
Typescript。另外，函数被编译为指令以快速执行。这使 Vim9 脚本快很多，多达百倍。

这里的基本想法是脚本文件包含只用于脚本文件内部的私有项目，和可被脚本文件之外使
用的导出项目。然后，这些导出项目可用于导入它们的脚本里。这样什么在哪里定义就会
很清晰。

让我们从一个例子开始，有一个导出函数和有一个私有函数的脚本: 
<code class="example"></code>
<code class="example">        vim9script</code>
<code class="example"></code>
<code class="example">        export def GetMessage(count: string): string</code>
<code class="example">           var nr = str2nr(count)</code>
<code class="example">           var result = $'To {nr} we say '</code>
<code class="example">           result ..= GetReply(nr)</code>
<code class="example">           return result</code>
<code class="example">        enddef</code>
<code class="example"></code>
<code class="example">        def GetReply(nr: number): string</code>
<code class="example">          if nr == 42</code>
<code class="example">             return 'yes'</code>
<code class="example">          elseif nr = 22</code>
<code class="example">             return 'maybe'</code>
<code class="example">          else</code>
<code class="example">             return 'no'</code>
<code class="example">          endif</code>
<code class="example">        enddef</code>
<code class="example"></code>
 <a href="vim9.html#vim9script">vim9script</a>  命令是必须的， <code class="badlink">export</code>  只能用于  <a href="vim9.html#Vim9">Vim9</a>  脚本里。

`export def GetMessage(...` 行以  <code class="badlink">export</code>  开始，意味着此函数可以被其它脚本调
用。`def GetPart(...` 行不以  <code class="badlink">export</code>  开头，意味着这是局部于脚本的函数，只能在
此脚本文件中使用。

现在说说导入此脚本的脚本。此例中使用如下布局，这适用于放在 "pack" 目录之下的插
件:
        .../plugin/theplugin.vim
        .../lib/getmessage.vim

假定 "..." 目录已添加进 <a href="options.html#'runtimepath'">'runtimepath'</a>，Vim 会寻找 "plugin" 目录里的插件并载入
"theplugin.vim"。Vim 并不识别 "lib" 目录，你可以在那里放任何脚本。

上面导出 GetMessage() 的脚本放在 lib/getmessage.vim 里。GetMessage() 函数在
plugin/theplugin.vim 里调用: 
<code class="example"></code>
<code class="example">        vim9script</code>
<code class="example"></code>
<code class="example">        import "../lib/getmessage.vim"</code>
<code class="example">        command -nargs=1 ShowMessage echomsg getmessage.GetMessage(&lt;f-args&gt;)</code>
<code class="example"></code>
 <code class="badlink">import</code>  命令使用了相对路径，因为它以 "../" 开始，也就是上一层目录。其它类型的
路径可见  <a href="vim9.html#:import">:import</a>  命令。

现在试试插件提供的命令吧: 
<code class="example">        ShowMessage 1</code>
<code class="section">        To 1 we say no </code>

<code class="example">        ShowMessage 22</code>
<code class="section">        To 22 we say maybe </code>

<code class="note">注意</code> GetMessage() 使用了导入脚本名字 "getmessage" 作为前缀。这样，对每个导入函
数，你会知道它是从哪里导入的。如果导入多个脚本，每个脚本都可以定义自己的
GetMessage() 函数: 
<code class="example"></code>
<code class="example">        vim9script</code>
<code class="example"></code>
<code class="example">        import "../lib/getmessage.vim"</code>
<code class="example">        import "../lib/getother.vim"</code>
<code class="example">        command -nargs=1 ShowMessage echomsg getmessage.GetMessage(&lt;f-args&gt;)</code>
<code class="example">        command -nargs=1 ShowOther echomsg getother.GetMessage(&lt;f-args&gt;)</code>
<code class="example"></code>
如果导入脚本名字太长或者需要在很多地方使用，可以通过 "as" 参数来缩短名字: 
<code class="example">        import "../lib/getmessage.vim" as msg</code>
<code class="example">        command -nargs=1 ShowMessage echomsg msg.GetMessage(&lt;f-args&gt;)</code>
<code class="example"></code>
<code class="example"></code>
重 载

记住一件事: 导入的 "lib/getmessage.vim" 脚本只会载入一次。第二次载入时会跳过，
因为里面的项目都已经创建了。不管是在其它脚本还是同一脚本里再次执行导入命令，都
是如此。

对插件的使用这是高效的，但在插件开发期间，这意味着导入 "lib/getmessage.vim" 之
后对它的修改不会生效。为此，只能退出 Vim 后重启。(理据: 脚本定义的项目可能在编
译过的函数里使用，再次载入脚本可能会破坏这些函数)。


使 用 全 局 值

有时需要使用全局变量或函数，以便在任何地方都能使用。一个全局变量的好例子是控制
插件行为的首选项。为了避免其它脚本使用的重名，使用别人不太可能使用的前缀。例
如，如果插件是 "mytags" 的话: 
<code class="example"></code>
<code class="example">        g:mytags_location = '$HOME/project'</code>
<code class="example">        g:mytags_style = 'fast'</code>
<code class="example"></code>
</section><hr class="doubleline" /><section class=inner>
<h4><b class="vimtag"> <a name="52.2">52.2</a> </b>  自动载入</h4>
在把大脚本分割为若干部分后，使用脚本时所有行仍然被载入和执行。每个  <code class="badlink">import</code>  都
会载入被导入的脚本以找到其中定义的项目。虽然这样容易早点发现错误，但颇耗时。如
果提供的功能不常用，这很浪费。

 <code class="badlink">import</code>  除了立即载入脚本之外，也可以延迟载入。使用上面的例子，只要在
plugin/theplugin.vim 里改一处就可以了: 
<code class="example">        import autoload "../lib/getmessage.vim"</code>
<code class="example"></code>
脚本其余部分无须改动。不过，不会检查类型。甚至 GetMessage() 函数存在与否，也是
在实际使用时才会检查。需要你自己决定是快速启动还是早点发现错误更加重要。也可以
在检查完一切都正常之后再加上 "autoload" 参数。


自 动 载 入 目 录

另一种使用自动载入的形式，脚本名既不是绝对也不是相对路径: 
<code class="example">        import autload "monthlib.vim"</code>
<code class="example"></code>
会在 <a href="options.html#'runtimepath'">'runtimepath'</a> 的 autoload 目录下搜索脚本 "monthlib.vim"。Unix 一个常见目
录是 "~/.vim/autoload"。也会在 <a href="options.html#'packpath'">'packpath'</a> 里 "start" 之下搜索脚本。

这种形式的主要好处是容易和其它脚本共享。但要确保脚本名唯一，因为 Vim 会在
<a href="options.html#'runtimepath'">'runtimepath'</a> 下搜索所有的 "autoload" 目录，如果使用插件管理器管理多个插件，它
会在 <a href="options.html#'runtimepath'">'runtimepath'</a> 下新增目录，每个目录都可能有 "autoload" 目录。

没有自动载入的话: 
<code class="example">        import "monthlib.vim"</code>
<code class="example"></code>
Vim 会在 <a href="options.html#'runtimepath'">'runtimepath'</a> 的 import 目录下搜索脚本 "monthlib.vim"。<code class="note">注意</code> 这种形式
里，加上或去掉 "autoload" 关键字会影响脚本寻找的位置。而使用相对或绝对路径的形
式时位置是不会改变的。

</section><hr class="doubleline" /><section class=inner>
<h4><b class="vimtag"> <a name="52.3">52.3</a> </b>  不经导入/导出的自动载入</h4>
                                                <b class="vimtag"> <a name="write-library-script">write-library-script</a> </b>
在导入/导出机制引入之前，已经有另一套有用的机制，有些用户会觉得它比较简单些。
基本想法是调用一个有特殊名字的函数，然后这个函数在某自动载入脚本中。我们把这种
脚本称为库脚本。

此自动载入机制是基于有 "#" 字符的函数名的: 
<code class="example"></code>
<code class="example">        mylib#myfunction(arg)</code>
<code class="example"></code>
Vim 会识别有内嵌 "#" 字符的函数名，如果该函数还没有定义，查找 <a href="options.html#'runtimepath'">'runtimepath'</a>
里的 "autoload/mylib.vim"。该脚本必须定义 "mylib#myfunction()" 函数。显然
"mylib" 名字是 "#" 之前的部分，加上 ".vim" 后就被用作脚本名。

在 mylib.vim 脚本里可以放上许多其它函数，你可以自由组织库脚本的函数。但必须使
函数名 "#" 前面的部分匹配脚本名。否则 Vim 无法知道载入哪个脚本。这是和导入/导
出机制不同之处。

如果你真的热情高涨写了很多库脚本，现在可能想要用子目录吧。例如: 
<code class="example"></code>
<code class="example">        netlib#ftp#read('somefile')</code>
<code class="example"></code>
这里脚本名取自函数名最后 "#" 之前的部分。中间的 "#" 用斜杠替代，最后加上
".vim"。这样得到的名字就是 "netlib/ftp.vim"。Unix 上，这里使用的库脚本可以是:

        ~/.vim/autoload/netlib/ftp.vim

其中的函数应该如此定义: 
<code class="example"></code>
<code class="example">        def netlib#ftp#read(fname: string)</code>
<code class="example">                "  用 ftp 读入文件 fname</code>
<code class="example">        enddef</code>
<code class="example"></code>
<code class="note">注意</code>定义所用的函数名必须和调用的函数名完全相同。最后一个 '#' 之前的部分必须准
确匹配子目录和脚本名。

同样的机制可以用来定义变量: 
<code class="example"></code>
<code class="example">        var weekdays = dutch#weekdays</code>
<code class="example"></code>
会载入脚本 "autoload/dutch.vim"，它应该包含这样的内容: 
<code class="example"></code>
<code class="example">        var dutch#weekdays = ['zondag', 'maandag', 'dinsdag', 'woensdag',</code>
<code class="example">                \ 'donderdag', 'vrijdag', 'zaterdag']</code>
<code class="example"></code>
进一步的阅读可见:  <a href="userfunc.html#autoload">autoload</a> 。

</section><hr class="doubleline" /><section class=inner>
<h4><b class="vimtag"> <a name="52.4">52.4</a> </b>  可用的其它机制</h4>
有些人觉得维护多个文件很麻烦，而把所有内容放在一个脚本。为了避免这样引起的启动
时的延迟，有一个机制只定义很小的一部分，而其余部分的载入会延迟到实际使用的时
候。 <b class="vimtag"> <a name="write-plugin-quickload">write-plugin-quickload</a> </b>

基本的方法是调用插件两次。第一次定义用户命令和映射，提供需要的功能。第二次定义
实现这些功能的函数。

听起来很吓人，快速载入意味着载入两次！我们的意思是，第一次载入很快，把脚本的大
部分内容延迟到第二次才载入，只有实际使用这些功能时才会这么做。当然，如果你总是
用这些功能，实际上更慢了！

这里使用 FuncUndefined 自动命令。和上述的  <a href="userfunc.html#autoload">autoload</a>  功能不同。

下例演示如何这是如何完成的: 
<code class="example"></code>
<code class="example">        " 演示快速载入的 Vim 全局插件</code>
<code class="example">        " Last Change:  2005 Feb 25</code>
<code class="example">        " Maintainer:   Bram Moolenaar &lt;Bram@vim.org&gt;</code>
<code class="example">        " License:      This file is placed in the public domain.</code>
<code class="example"></code>
<code class="example">        if !exists("s:did_load")</code>
<code class="example">                command -nargs=* BNRead  call BufNetRead(&lt;f-args&gt;)</code>
<code class="example">                map &lt;F19&gt; :call BufNetWrite('something')&lt;CR&gt;</code>
<code class="example"></code>
<code class="example">                let s:did_load = 1</code>
<code class="example">                exe 'au FuncUndefined BufNet* source ' .. expand('&lt;sfile&gt;')</code>
<code class="example">                finish</code>
<code class="example">        endif</code>
<code class="example"></code>
<code class="example">        function BufNetRead(...)</code>
<code class="example">                echo 'BufNetRead(' .. string(a:000) .. ')'</code>
<code class="example">                " 读入功能在此</code>
<code class="example">        endfunction</code>
<code class="example"></code>
<code class="example">        function BufNetWrite(...)</code>
<code class="example">                echo 'BufNetWrite(' .. string(a:000) .. ')'</code>
<code class="example">                " 写回功能在此</code>
<code class="example">        endfunction</code>
<code class="example"></code>
第一次载入脚本时，没有设置 "s:did_load"。这时执行 "if" 和 "endif" 之间的命令。
它以  <a href="repeat.html#:finish">:finish</a>  命令结束，这样脚本的其余部分不再执行。

第二次载入脚本时，"s:did_load" 已经存在，这时执行 "endif" 之后的命令。这里定义
(可能很长的) BufNetRead() 和 BufNetWrite() 函数。

如果把该脚本放到你的 plugin 目录，Vim 启动时会执行它。下面列出发生事件的序列:

1. 启动期间执行脚本时，定义 "BNRead" 命令并映射 <code class="special">&lt;F19&gt;</code> 键。定义  <a href="autocmd.html#FuncUndefined">FuncUndefined</a> 
   自动命令。":finish" 命令使脚本提前终止。

2. 用户输入 BNRead 命令或者按了 <code class="special">&lt;F19&gt;</code> 键。BufNetRead() 或 BufNetWrite() 函数会
   被调用。

3. Vim 找不到这些函数并因此激活了  <a href="autocmd.html#FuncUndefined">FuncUndefined</a>  自动命令事件。因为模式
   "BufNet*" 匹配要调用的函数，执行命令 "source fname"，其中 "fname" 被赋予脚
   本的名字，不管它实际在何处都没问题。这是因为该名字来自 "<code class="special">&lt;sfile&gt;</code>" 扩展的结果
   (见  <a href="builtin.html#expand()">expand()</a> )。

4. 再次执行脚本。"s:did_load" 变量已经存在，此时定义函数。

<code class="note">注意</code>后来载入的函数匹配  <a href="autocmd.html#FuncUndefined">FuncUndefined</a>  自动命令的模式。要确信其它插件没有定义
匹配此模式的函数。

</section><hr class="doubleline" /><section class=inner>
<h4><b class="vimtag"> <a name="52.5">52.5</a> </b>  在老式脚本中使用 Vim9 脚本                      <b class="vimtag"> <a name="source-vim9-script">source-vim9-script</a> </b></h4>
有些情况下，在老式脚本里你会想使用 Vim9 脚本里的项目。例如在 .vimrc 里初始化插
件。最好的方法是用  <a href="vim9.html#:import">:import</a> 。例如: 
<code class="example"></code>
<code class="example">        import 'myNicePlugin.vim'</code>
<code class="example">        call myNicePlugin.NiceInit('today')</code>
<code class="example"></code>
这会找到 Vim9 脚本文件里的 "NiceInit" 导出函数，并使之成为局部于脚本的项目
"myNicePlugin.NiceInit"。即使不给出 "s:"， <a href="vim9.html#:import">:import</a>  也总使用脚本命名空间，如果
"myNicePlugin.vim" 已经执行过，不会再次执行。

除了避免把任何项目放进全局命名空间 (因为命名冲突会导致意料之外的错误) 以外，这
也意味着无论其中的项目被导入多少次，一份脚本只执行一次。

有些情况下，例如为了测试用途，可能就想要直接执行 Vim9 脚本。这没问题，但你只能
得到其中的全局项目。该 Vim9 脚本要确保这些全局项目使用独一无二的名字。例如: 
<code class="example">        source ~/.vim/extra/myNicePlugin.vim</code>
<code class="example">        call g:NicePluginTest()</code>
<code class="example"></code>
</section><hr class="doubleline" /><section class=inner>
<h4></h4>下一章:  <a href="usr_90.html#usr_90.txt">usr_90.txt</a>   安装 Vim


版权: 参见  <a href="usr_01.html#manual-copyright">manual-copyright</a>   vim:tw=78:ts=8:noet:ft=help:norl:
</section>
</article>
<footer>
Generated by vim2html
</footer>
</body>
</html>
